﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ODataValidator.Rule.Helper
{
    public interface IEdmType
    {
        bool IsGoodWith(string text);
        bool IsGoodInJsonWith(string text);
    }

    public static class EdmTypeManager
    {
        static Dictionary<string, IEdmType> map;

        static EdmTypeManager()
        {
            map = new Dictionary<string,IEdmType>()
            {
                // v1 v2 Edm Types
                {"Edm.Binary", new EdmBinary()} ,
                {"Edm.Boolean", new EdmBoolean()} ,
                {"Edm.Byte", new EdmByte()} ,
                {"Edm.DateTime", new EdmDateTime()} ,
                {"Edm.Decimal", new EdmDecimal()} ,
                {"Edm.Double", new EdmDouble()} ,
                {"Edm.Single", new EdmSingle()} ,
                {"Edm.Guid", new EdmGuid()} ,
                {"Edm.Int16", new EdmInt16()} ,
                {"Edm.Int32", new EdmInt32()} ,
                {"Edm.Int64", new EdmInt64()} ,
                {"Edm.SByte", new EdmSByte()} ,
                {"Edm.String", new EdmString()} ,
                {"Edm.Time", new EdmTime()} ,
                {"Edm.DateTimeOffset", new EdmDateTimeOffset()} ,

                // v3 Edm Types go here
                // ...
            };
        }

        public static bool IsEdmSimpleType(string name)
        {
            return map.ContainsKey(name);
        }

        public static IEdmType GetEdmType(string name)
        {
            IEdmType type;
            bool found = map.TryGetValue(name, out type);
            if (!found)
            {
                type = null;
            }

            return type;
        }
    }

    class EdmBinary : IEdmType
    {
        // base64 encoded byte stream
        const string pattern_base64encoded = @"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$";
        const string pattern_binary_in_json = @"^""(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?""$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_base64encoded);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_binary_in_json);
        }
    }

    class EdmBoolean : IEdmType
    {
        const string pattern_boolean = @"^(1|0|true|false)$";
        const string pattern_boolean_in_json = @"^(true|false)$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_boolean);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_boolean_in_json);
        }
    }

    class EdmByte : IEdmType
    {
        const string pattern_byte = @"^[0-9]{1,3}$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            bool matched = Regex.IsMatch(text, pattern_byte);
            if (matched)
            {
                try
                {
                    byte r = Convert.ToByte(text);
                    return r != r - 1; // trick to ensure IL compiler won't optimize out the block
                }
                catch (OverflowException)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        public bool IsGoodInJsonWith(string text)
        {
            return this.IsGoodWith(text);
        }
    }

    class EdmDateTime : IEdmType
    {
        const string patternDateTime = @"^[0-9]{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|3[01])T(0?[0-9]|1[0-9]|2[0-4]):([0-5][0-9]|60)(:([0-5][0-9]|60)(\.[0-9]{1,7})?)?$";
        internal const string patternDateTimeInJson = @"^""\\/Date\([0-9]*([+\-][0-9]{4})?\)\\/""$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text.Trim(), patternDateTime);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text.Trim(), patternDateTimeInJson);
        }
    }

    class EdmDecimal : IEdmType
    {
        const string patternDecimal = @"^(-)?[0-9]{1,29}(\.[0-9]{1,29})?$";
        const string patternDecimalInJson = @"^""(-)?[0-9]{1,29}(\.[0-9]{1,29})?""$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text.Trim(), patternDecimal);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text.Trim(), patternDecimalInJson);
        }
    }

    class EdmDouble : IEdmType
    {
        const string pattern_nonDecimalPoint = @"(-)?[0-9]{1,17}(D|d)";
        const string pattern_nonExpDecimal = @"(-)?[0-9]*\.[0-9]*(D|d)";
        const string pattern_expDecimal = @"(-)?[0-9]{1,}\.[0-9]{,16}(e|E)(-)?[0-9]{1,3}(D|d)";
        const string pattern_nan = @"Nan(D|d)";
        const string pattern_negativeInfinity = @"-INF(D|d)";
        const string pattern_postiveInfinity = @"INF(D|d)";

        static string patternDouble;
        static string patternDoubleInJson;

        static EdmDouble()
        {
            patternDouble = string.Format(@"^(({0})|({1})|({2})|({3})|({4})|({5}))$",
                pattern_nonDecimalPoint,
                pattern_nonExpDecimal,
                pattern_expDecimal,
                pattern_nan,
                pattern_negativeInfinity,
                pattern_postiveInfinity);

            patternDoubleInJson = string.Format(@"^""(({0})|({1})|({2})|({3})|({4})|({5}))""$",
                pattern_nonDecimalPoint,
                pattern_nonExpDecimal,
                pattern_expDecimal,
                pattern_nan,
                pattern_negativeInfinity,
                pattern_postiveInfinity);
        }

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternDouble);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text.Trim(), patternDoubleInJson);
        }
    }

    class EdmSingle : IEdmType
    {
        const string pattern_nonDecimalPoint = @"^(-)?[0-9]{1,8}$";
        const string pattern_nonExpDecimal = @"^(-)?[0-9]*\.[0-9]*$";
        const string pattern_expDecimal = @"^(-)?[0-9]{1,}\.[0-9]{,8}(e|E)(-)?[0-9]{1,2}$";
        const string pattern_nan = @"^Nan$";
        const string pattern_negativeInfinity = @"^-INF$";
        const string pattern_postiveInfinity = @"^INF$";

        static string patternSingle;
        static string patternSingleInJson;

        static EdmSingle()
        {
            patternSingle = string.Format(@"^(({0})|({1})|({2})|({3})|({4})|({5}))$",
                pattern_nonDecimalPoint,
                pattern_nonExpDecimal,
                pattern_expDecimal,
                pattern_nan,
                pattern_negativeInfinity,
                pattern_postiveInfinity);

            patternSingleInJson = string.Format(@"^""(({0})|({1})|({2})|({3})|({4})|({5}))""$",
                pattern_nonDecimalPoint,
                pattern_nonExpDecimal,
                pattern_expDecimal,
                pattern_nan,
                pattern_negativeInfinity,
                pattern_postiveInfinity);
        }

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternSingle);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternSingleInJson);
        }
    }

    class EdmGuid : IEdmType
    {
        const string patternGuid = @"^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$";
        const string patternGuidInJson = @"^""[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}""$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternGuid);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternGuidInJson);
        }
    }

    class EdmInt16 : IEdmType
    {
        const string pattern_int16 = @"^(-)?[0-9]{1,5}$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_int16);
        }

        public bool IsGoodInJsonWith(string text)
        {
            return this.IsGoodWith(text);
        }
    }

    class EdmInt32 : IEdmType
    {
        const string pattern_int32 = @"^(-)?[0-9]{1,10}$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_int32);
        }

        public bool IsGoodInJsonWith(string text)
        {
            return this.IsGoodWith(text);
        }
    }

    class EdmInt64 : IEdmType
    {
        const string patternInt64 = @"^(-)?[0-9]{1,19}$";
        const string patternInt64InJson = @"^""(-)?[0-9]{1,19}""$";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternInt64);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternInt64InJson);
        }
    }

    class EdmSByte : IEdmType
    {
        const string pattern_sbyte = @"^(-)?[0-9]{1,3}$";

        public bool IsGoodWith(string text)
        {
            // I believe this is a TDI bug in spec to claim SByte as int32Lieral 
            // anyway, I would go with the common sense
            text = text.Trim();
            return Regex.IsMatch(text, pattern_sbyte);
        }

        public bool IsGoodInJsonWith(string text)
        {
            return this.IsGoodWith(text);
        }
    }

    class EdmString : IEdmType
    {
        // (UTF8-char)* ?
        const string patternStringInJson = @"^"".*""$";

        public bool IsGoodWith(string text)
        {
            // looks like everything is a string? which is not?
            return true;
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternStringInJson);
        }
    }

    class EdmTime : IEdmType
    {
        // 3.2.6 duration, http://www.w3.org/TR/xmlschema-2/#dt-value-space
        const string pattern_duration = @"^(-)?P([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M[0-9]+(\.[0-9]+)?S)?)?$";
        const string pattern_duration_constraint = @"[0-9][YMDHS]";

        static string patternTime;
        static string patternTimeInJson;

        static EdmTime()
        {
            patternTime = string.Format(@"^({0})$", pattern_duration);
            patternTimeInJson = string.Format(@"^""({0})""$", pattern_duration);
        }

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternTime) &&
                Regex.IsMatch(text, pattern_duration_constraint);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, patternTimeInJson) &&
                Regex.IsMatch(text, pattern_duration_constraint);
        }
    }

    class EdmDateTimeOffset : IEdmType
    {
        // 3.2.7 dateTime, http://www.w3.org/TR/xmlschema-2/#dt-value-space
        const string pattern_dateTime = @"-?([0-9]{4}|([1-9][0-9]{4,})-(1[0-2]|0[0-9])-((0[0-9])|[12][0-9]|3[01]))T([01][0-9]|2[0-4]):([0-5][0-9]|60):([0-5][0-9]|60)(\.[0-9]*[1-9])?(Z|([+-](0[0-9]|1[0-4]):([0-5][0-9]))";

        public bool IsGoodWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, pattern_dateTime);
        }

        public bool IsGoodInJsonWith(string text)
        {
            text = text.Trim();
            return Regex.IsMatch(text, EdmDateTime.patternDateTimeInJson);
        }
    }
}
